package com.xiangxiao.rpan.authority.config;

import com.xiangxiao.rpan.authority.oauth.*;
import com.xiangxiao.rpan.authority.service.impl.UserDetailServiceImpl;
import com.xiangxiao.rpan.authority.service.impl.UserserviceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;

import java.util.concurrent.TimeUnit;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * @auther xiangxiao
 * @email 573768011@qq.com
 * @data 2023/3/21 20:02
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfiguration extends AuthorizationServerConfigurerAdapter {
  @Autowired
  @Qualifier("authenticationManagerBean")
  private AuthenticationManager authenticationManager;

  @Autowired
  private StringRedisTemplate redisTemplate;

  @Autowired
  private UserDetailServiceImpl userDetailsService;

  @Bean
  public TokenStore tokenStore() {
    return new RpanRedisTokenStore(redisTemplate);
  }

  ;

  @Autowired
  private LocalLoginExceptionTranslator excptionTranslator;

  @Autowired
  private UserserviceImpl userService;

  @Autowired
  private DataSource dataSource;

  @Autowired
  private Environment environment;

  @Bean
  public TokenEnhancer tokenEnhancer() {
    return new OauthTokenEnhancer();
  }

  @Bean
  @Primary
  public DefaultTokenServices tokenServices() {
    RpanTokenService tokenService = new RpanTokenService(tokenStore(), tokenEnhancer(), userService);
    // 获取登录策略配置,login_only_one_device为true表示一个账号在任意一个端上只能登录一台设备,多台设备登录互相踢下线
    String loginOnlyOneDeviceInfo = environment.getProperty("login_only_one_device", "true");
    if (StringUtils.isNotBlank(loginOnlyOneDeviceInfo)) {
      try {
        boolean loginOnlyOneDevice = Boolean.parseBoolean(loginOnlyOneDeviceInfo);
        tokenService.setLoginOnlyOneDevice(loginOnlyOneDevice);
      } catch (Exception e) {
        // 不进行处理，
      }
    }

    return tokenService;
  }

  /**
   * configure(AuthorizationServerEndpointsConfigurer endpoints) ：用来配置授权（authorization）以及令牌（token）的访问端点和令牌服务(token services)，还有token的存储方式(tokenStore)
   * AuthorizationServerEndpointsConfigurer其实是一个装载类，告装载Endpoints所有相关的类配置（AuthorizationServer、
   * TokenServices、TokenStore、ClientDetailsService、UserDetailsService）
   * @param endpoints
   * @throws Exception
   */
  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    TokenStore tokenStore = this.tokenStore();
    endpoints.tokenStore(tokenStore).authenticationManager(this.authenticationManager)
        .userDetailsService(userDetailsService);
    // 添加返回额外信息的Enhancer
    TokenEnhancer tokenEnhancer = tokenEnhancer();
    endpoints.tokenEnhancer(tokenEnhancer);
    // 添加自定义的token处理服务
    endpoints.tokenServices(tokenServices());
    endpoints.exceptionTranslator(excptionTranslator);
  }

  /**
   * 配置AuthorizationServerSecurityConfigurer，用来配置令牌端点的安全约束
   **/
  @Override
  public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    security.allowFormAuthenticationForClients().tokenKeyAccess("permitAll()")
        .checkTokenAccess("isAuthenticated()");
    security.accessDeniedHandler(new LocalAccessDeniedHandler());
    security.authenticationEntryPoint(new LocalAuthenticationEntryPoint());
  }

  /**
   * 配置ClientDetailsServiceConfigurer，用来配置客户端详情服务，客户端详情信息在这里进行初始化
   * <p>
   * 这个如果配置支持allowFormAuthenticationForClients的，且url中有client_id和client_secret的会走ClientCredentialsTokenEndpointFilter来保护
   * 如果没有支持allowFormAuthenticationForClients或者有支持但是url中没有client_id和client_secret的，走basic认证保护
   *
   * @param clients
   * @throws Exception
   */
  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

    clients.jdbc(dataSource)
        .inMemory()
        .withClient("client")
        .redirectUris("http://www.baidu.com")
        //此处的scopes是无用的，可以随意设置
        .scopes("all", "read", "write")
        .secret("secret")//401错误，我的解决办法是这个，仅供参考
        .authorizedGrantTypes("password", "authorization_code", "refresh_token", "app");     // 该client允许的授权类型
  }
}
